const { Document } = require('camo')
const battleAIs = require('../battle/ai')
const log = require('../util/log')

const gm = require('gm')
const request = require('request')
const tinygradient = require('tinygradient')
const tinycolor = require('tinycolor2')

const healthGradient = tinygradient(['#BA0020', '#ffbf00', '#77dd77'])
const colorDead = '#36454f'

const BACKUP_AVATAR_URL = 'https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png'

// A Character refers to both a player and an enemy. The difference is in
// their `ai` that controls their actions (for players, this is DiscordAI).
class Character extends Document {
  constructor() {
    super()

    this.schema({
      discordID: {type: String, required: false},
      battleAI: {type: String, required: true, choices: Object.keys(battleAIs)},

      partyID: {type: String, required: false}, // Automatically generated

      health: {type: Number, default: 5, min: 0},
      maxHealth: {type: Number, default: 5, min: 1},

      moveIDs: {type: [String]},
    })
  }

  // Rather than a string (this.battleAI), returns the class itself.
  get BattleAI() {
    return battleAIs[this.battleAI]
  }

  async modHealth(by, guild) {
    this.health += by

    if (this.health > this.maxHealth) this.health = this.maxHealth
    if (this.health < 0)              this.health = 0

    if (guild) await this.healthUpdate(guild)

    return this.healthState
  }

  async healthUpdate(guild) {
    await this.save()
    if (!this.discordID) return

    const member = guild.members.get(this.discordID)

    // Remove existing health role, if any
    const roleExisting = member.roles.find(role => role.name.endsWith(' health'))
    if (roleExisting) await roleExisting.delete()

    // Reuse/create a health role and grant it
    const roleName = `${this.health}/${this.maxHealth} health`
    const role = member.roles.find(role => role.name === roleName)
      || await guild.createRole({
        name: roleName,
        color: this.health === 0
          ? colorDead
          : tinycolor(healthGradient.rgbAt(this.health / this.maxHealth))
            .toHexString(),
      })

    await member.addRole(role)
  }

  get healthState() {
    if (this.health === this.maxHealth) return 'healthy'
    else if (this.health === 1)         return 'peril'
    else if (this.health === 0)         return 'dead'
    else                                return 'injured'
  }

  getName(guild) {
    // FIXME: names for characters without accociated discord users
    return guild.members.get(this.discordID).displayName
  }

  // "Label" of a character, to be displayed in messages.
  async getLabel(game) {
    const emote = await this.getEmote(game)
    const role = await this.getRole(game)

    return `${emote} ${role}`
  }

  // Finds/creates a Discord role with the same name as the character and with
  // the same color as the character's party.
  async getRole(game, forceUpdate = false) {
    if (this._discordRole && !forceUpdate) {
      return this._discordRole
    }

    // Create the role
    const name = this.getName(game.guild)
    const party = await this.getParty()
    const role = await game.guild.createRole({name, color: party.colorHex})

    // Give this member the role
    const member = await game.guild.members.get(this.discordID)
    await member.addRole(role)

    return this._discordRole = role
  }

  // Returns a little icon emote based on the character's avatar.
  // TODO: implement this for characters that aren't discord-backed
  async getEmote(game) {
    if (!this.discordID) throw new Error() // FIXME

    try {
      const member = game.guild.members.get(this.discordID)
      const graphic = gm(request(member.user.avatarURL))
        .resize(48, 48)
        .stream('PNG')
      const emote = await game.emoteStore.get(`char${this._id}`, graphic)

      return emote
    } catch (err) {
      log.warning(`Error while generating character emote:\n${err.message}`)

      const graphic = gm(request(BACKUP_AVATAR_URL))
        .resize(48, 48)
        .stream('PNG')
      const emote = await game.emoteStore.get(`char${this._id}`, graphic)

      return emote
      //return _.sample(['❤️', '💛', '💙', '💜', '💚'])
    }
  }

  async getParty() {
    const Party = require('./party') // I don't like this any more than you do, but recursive requires DESTROY Node.js. Sorry.

    let party
    if (!this.partyID) {
      // Create a one-man party
      party = await Party.of([this])
    }

    return party || Party.findOne({_id: this.partyID})
  }
}

module.exports = Character
